/**
 * Implementation of the Data Link Layer interface.
 *
 * @author Timothy Flynn (trflynn@wpi.edu)
 * @version April 11, 2012
 */
#include <stdlib.h>
#include <string>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
using std::string;

#include "DataLinkLayer.h"

// Private namespace for helper functions
namespace {
	volatile bool tOut;

	/**
	 * Signal a timer has run out.
	 */
	void timerEnd(int i) {
		tOut = true;
	}

	/**
	 * between() taken from Tanenbaum.
	 */
	bool between(short a, short b, short c) {
		/* Return true if (a <= b < c circularly: false otherwise. */
		if (((a <= b) && (b < c)) || ((c < a) && (a <= b)) || ((b < c) && (c < a)))
			return(true);
		else
			return(false);
	}
}

/**
 * Constructor.
 */
DataLinkLayer::DataLinkLayer() {
	nextToSend = 0;
	ackExpected = 0;
	frameExpected = 0;
}

/**
 * Destructor.
 */
DataLinkLayer::~DataLinkLayer() {
	// TODO
}

/**
 * Run the Data Link Layer thread. For now, very simply just poll for things to
 * pass along.
 */
void DataLinkLayer::run() {
	while(1) {
		// Get packet from network layer
		Packet *p;
		if((p = fromNetworkLayer()) != NULL) {
			// Convert to frame(s) and send to physical layer
			createFrames(p);
		}
		toPhysicalLayer();

		// Get frame from physical layer
		Frame *f;
		if((f = fromPhysicalLayer()) != NULL) {
			// Convert to packet and send to network layer
			toNetworkLayer(createPacket(f));
		}
	}
}

/**
 * Run the Data Link Layer thread, using the Go Back N protocol.
 */
void DataLinkLayer::goBackN() {
	Frame *r = new Frame();
	short nbuffered = 0;

	Packet *buffer[MAX_SEQ];
	for(int i=0; i<MAX_SEQ; i++) {
		buffer[i] = new Packet();
	}

	enableNetworkLayer(true);

	while(1) {
		// Wait for something to happen
		Event event = None;
		waitForEvent(&event);

		switch(event) {
			// Network layer has given us a packet
			case NetworkLayerReady:
				buffer[nextToSend] = fromNetworkLayer();
				nbuffered++;

				// next to send incremented and
				// timer started in createFrames()
				createFrames(buffer[nextToSend]);
				toPhysicalLayer();
				break;

			// Physical layer has given us a frame
			case FrameArrival:
				r = fromPhysicalLayer();
				if(r->seq == frameExpected) {
					toNetworkLayer(createPacket(r));
					INC(frameExpected);
				}

				// Ack n implies n-1, n-2, etc; stop timers
				while (between(ackExpected, r->ack, nextToSend)) {
					nbuffered--;
					stopTimer(ackExpected);
					INC(ackExpected);
				}

				break;

			// Ignore bad frames
			case CheckSumError:
				break;

			// Retransmit all outstanding frames
			case Timeout:
				nextToSend = ackExpected;

				for(int i=0; i<nbuffered; i++) {
					createFrames(buffer[nextToSend]);
					toPhysicalLayer();
				}

				break;

			// ?
			default:
				break;

		}

		// Check if we need to slow the network layer
		if(nbuffered < MAX_SEQ) {
			enableNetworkLayer(true);
		} else {
			enableNetworkLayer(false);
		}
	}
}

/**
 * Create 1 or 2 frames from a packet. Store in toSend().
 */
void DataLinkLayer::createFrames(Packet *p) {
	// How many frames to make?
	int len = p->length;
	int n = len / MAX_FRAME_SZ;
	if(len % MAX_FRAME_SZ > 0) n++;

	string *str = new string(p->data);
	int currLen = (len > MAX_FRAME_SZ ? MAX_FRAME_SZ : len);

	// Create each frame
	for(int i=0; i<n; i++) {
		Frame *f = new Frame();
		f->type = p->type;
		f->end = (i == n-1);
		f->seq = nextToSend;
		f->ack = (frameExpected + MAX_SEQ) % (MAX_SEQ + 1);
		f->length = currLen;
		f->data = (char *)malloc((f->length + 1) * sizeof(char));
		memcpy(f->data, (char *)str->substr(i*MAX_FRAME_SZ, currLen).c_str(), currLen);
		f->data[f->length] = '\0';

		len -= currLen;
		currLen = (len > MAX_FRAME_SZ ? MAX_FRAME_SZ : len);

		toSend.push(f);
		startTimer(nextToSend);
		INC(nextToSend);
	}
}

/**
 * Create a packet from a frame. Just copy data over.
 */
Packet *DataLinkLayer::createPacket(Frame *f) {
	Packet *p = new Packet();
	p->type = f->type;
	p->end = f->end;
	p->seq = f->seq;
	p->length = f->length;
	p->data = (char *)malloc((p->length + 1) * sizeof(char));
	memcpy(p->data, f->data, p->length);
	p->data[p->length] = '\0';

	return p;
}

/**
 * Read a packet from the network layer.
 */
Packet *DataLinkLayer::fromNetworkLayer() {
	Packet *p = NULL;

	pthread_mutex_lock(&sendPktM);
	if(!sendPktQ.empty()) {
		p = sendPktQ.front();
		sendPktQ.pop();
	}
	pthread_mutex_unlock(&sendPktM);

	return p;
}

/**
 * Send a packet to the network layer.
 */
void DataLinkLayer::toNetworkLayer(Packet *p) {
	pthread_mutex_lock(&recvPktM);
	recvPktQ.push(p);
	pthread_mutex_unlock(&recvPktM);
}

/**
 * Read a frame from the physical layer.
 */
Frame *DataLinkLayer::fromPhysicalLayer() {
	Frame *f = NULL;

	pthread_mutex_lock(&recvFrameM);
	if(!recvFrameQ.empty()) {
		f = recvFrameQ.front();
		recvFrameQ.pop();
	}
	pthread_mutex_unlock(&recvFrameM);

	return f;
}

/**
 * Send all frames in toSend() to physical layer.
 */
void DataLinkLayer::toPhysicalLayer() {
	pthread_mutex_lock(&sendFrameM);
	while(!toSend.empty()) {
		sendFrameQ.push(toSend.front());
		toSend.pop();
	}
	pthread_mutex_unlock(&sendFrameM);
}

/**
 * Wait for some event to occur.
 */
void DataLinkLayer::waitForEvent(Event* e) {
	while((*e) == None) {
		if(tOut) {
			tOut = false;
			(*e) = Timeout;
		}

		if(pthread_mutex_trylock(&recvFrameM) == 0) {
			if(!recvFrameQ.empty()) {
				if(recvFrameQ.front()->crc == 0) {
					(*e) = FrameArrival;
				} else {
					(*e) = CheckSumError;
				}
			}
			pthread_mutex_unlock(&recvFrameM);
		}

		if(NLEnabled && pthread_mutex_trylock(&sendPktM) == 0) {
			if(!sendPktQ.empty()) {
				(*e) = NetworkLayerReady;
			}
			pthread_mutex_unlock(&sendPktM);
		}
	}
}

/**
 * Start a timer with the given identifier.
 */
void DataLinkLayer::startTimer(int id) {
	// Set the timer to call timeout() when over
	signal(SIGALRM, timerEnd);

	struct itimerval it;
	it.it_value.tv_sec = 1; it.it_value.tv_usec = 0;
	it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0;
	setitimer(ITIMER_REAL, &it, NULL);
}

/**
 * Stop a timer with the given identifier.
 */
void DataLinkLayer::stopTimer(int id) {
	struct itimerval it;
	it.it_value.tv_sec = 0; it.it_value.tv_usec = 0;
	it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0;
	setitimer(ITIMER_REAL, &it, NULL);
}

/**
 * Toggle whether or not we listen to the network layer.
 */
void DataLinkLayer::enableNetworkLayer(bool en) {
	NLEnabled = en;
}
